Introduction

Welcome to the Exploratory Data Analysis of the CRAN historical data set. As you may already know, CRAN is a network of servers around the world which store code and documentation for the R packages over time. As of writing this EDA, CRAN had just over 18,000 packages available in it’s repository.

CRAN Website

Heads or Tails has done a great job of grabbing historical data, cleaning it up and preparing it for us R enthusiasts. Read about the approach he followed in his blogpost.

Initial Setup

Read through the initial setup in the 4 tabs below.

Libraries

First, some I import some useful libraries and set some plotting defaults.

# Data Manipulation
library(dplyr)
library(tidyr)
library(readr)
library(skimr)
library(purrr)
library(stringr)
library(urltools)

# Plots
library(ggplot2)
library(naniar)
library(packcircles)
library(ggridges)

# Tables
library(reactable)

# Settings
theme_set(theme_minimal(
  base_size = 14
))

Read In

Let’s start be reading in the data. There are two CSV files in this dataset. From his dataset page:

  • cran_package_overview.csv: all R packages currently available through CRAN, with (usually) 1 row per package…
  • cran_package_history.csv: version history of virtually all packages in the previous table...
hist_dt <- read_csv(
  "../input/cran_package_history.csv",
  col_types = cols(
    package = col_character(),
    version = col_character(),
    date = col_date(format = "%Y-%m-%d"),
    repository = col_character()
  )
)
ov_dt <- read_csv(
  "../input/cran_package_overview.csv",
  col_types = cols(
    package = col_character(),
    version = col_character(),
    depends = col_character(),
    imports = col_character(),
    license = col_character(),
    needs_compilation = col_logical(),
    author = col_character(),
    bug_reports = col_character(),
    url = col_character(),
    date_published = col_date(format = "%Y-%m-%d"),
    description = col_character(),
    title = col_character()
  )
)

Quick View

I love to take the first peek into a dataset with the amazing {skimr} package. We can see that we have the right data types set for all the columns, dates have been imported correctly.

We can see in the history data that the 1st package reported on CRAN was in 22 years ago on 1998-02-25! Furthermore, the overview tells us there’s a package {pack} last published/updated on 2008-09-08.

While there’s no missing data in the history dataset, there are a bunch of missing values in the overview dataset. Let’s explore this a bit more.

skimr::skim(hist_dt)
── Data Summary ────────────────────────
                           Values 
Name                       hist_dt
Number of rows             119464 
Number of columns          4      
_______________________           
Column type frequency:            
  character                3      
  Date                     1      
________________________          
Group variables            None   

── Variable type: character ─────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min max empty n_unique whitespace
1 package               0             1   2  32     0    18372          0
2 version               0             1   3  15     0    10074          0
3 repository            0             1   4   7     0        2          0

── Variable type: Date ──────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median     n_unique
1 date                  0             1 1998-02-25 2022-07-17 2018-01-22     7119
skimr::skim(ov_dt)
── Data Summary ────────────────────────
                           Values
Name                       ov_dt 
Number of rows             18388 
Number of columns          12    
_______________________          
Column type frequency:           
  character                10    
  Date                     1     
  logical                  1     
________________________         
Group variables            None  

── Variable type: character ─────────────────────────────────────────────────────────────────────────────────
   skim_variable n_missing complete_rate min  max empty n_unique whitespace
 1 package               0         1       2   32     0    18371          0
 2 version               0         1       3   14     0     2112          0
 3 depends            4862         0.736   2  218     0     4289          0
 4 imports            4026         0.781   2  573     0    12041          0
 5 license               0         1       3   54     0      159          0
 6 author                0         1       4 4096     0    15652          0
 7 bug_reports       10760         0.415  11   81     0     7578          0
 8 url                8426         0.542   4  466     0     9548          0
 9 description           0         1       5 7372     0    18368          0
10 title                 0         1       5  210     0    18306          0

── Variable type: Date ──────────────────────────────────────────────────────────────────────────────────────
  skim_variable  n_missing complete_rate min        max        median     n_unique
1 date_published         1          1.00 2008-09-08 2022-07-17 2021-03-22     2542

── Variable type: logical ───────────────────────────────────────────────────────────────────────────────────
  skim_variable     n_missing complete_rate  mean count                
1 needs_compilation         0             1 0.239 FAL: 13991, TRU: 4397

Data Quality

My favorite way of exploring missing data is to make it visible, using Nick Tierney’s amazing {naniar} package. There are a few columns with missing data. Let’s look at these more closely.

  • depends and imports have roughly a quarter of the data as <NA>. These are packages which roughly have no external dependencies. The difference between the two can get a bit complex; best to learn about it in Hadley’s chapter here.
  • Roughly half of bug_reports and url are missing. These don’t seem to be data issues as much as authors who don’t have a place to issue bugs or website for their package respectively.
  • date_published has only 1 row with missing, which seems like a data quality spill.
ov_dt |> 
  dplyr::arrange(date_published) |> 
  vis_miss()

Interesting Questions

Since this is an open ended exploration - unlike other EDA with the purpose of building a predictive model - before I continue to the plotting, I’d like to posit some questions which will guide the flow of further work. The first five questions are from Martin’s blog, with further questions which I think would be interesting to explore.

  1. How long did packages take from their first release to version 1.0?
  2. Which packages have had the most version updates?
  3. What type of packages were most frequent in different years?
  4. Who are the most productive authors?
  5. Can you predict the growth toward 2025?
  6. What license is most used? Has there been a change over time?
  7. How many packages use all CAPS, all small, or a mixture?
  8. How have the dependencies & imports changed over time?
  9. Which repositories do packages use? Github/Bitbucket etc. How do these vary over time?
  10. Do packages have URLs for bug reports?
  11. Is there any temporal patterns to when versions are submitted to CRAN?
  12. Have titles & descriptions gotten longer over time?
  13. Do authors use minor versions?

Feature Development

To aid answering many of these, I first need to create a few new features in the overview data set.

Read about the feature development in the tabs below. We go from 12 columns to 29 columns in the overview data set.

Version Numbers

Per the R package section in Hadley’s book, “an R package version is a sequence of at least two integers separated by either . or -. For example, 1.0 and 0.9.1-10 are valid versions, but 1 and 1.0-devel are not”. Typically, packages do follow the three number format of <major>.<minor>.<patch>. I’m making an assumption this is true, just to simplify things. I have a feeling it’ll capture most of the cases.

This feature could help answer questions about version number progressions.

split_versions <- function(dat) {
  stopifnot("version" %in% names(dat))
  
  dat |>
    separate(
      version,
      into =
        c("major", "minor", "patch"),
      sep = "\\.",
      extra = "merge", # for versions like 1.0.3-3000, keep the '3-3000' together in the 3rd col
      fill = "right",
      remove = FALSE
    )
}

ov_dt <- ov_dt |> split_versions()
hist_dt <- hist_dt |> split_versions()
head(hist_dt) |> 
  reactable(compact = TRUE)

Dependies & Imports

For the last published version of the package, how many dependencies and/and imports does each package have? My hypothesis is that packages in the past relied on lesser dependencies since they were more likely than not written in base R. With the recent explosion of adoption of R, and the adoption of the tidyverse framework, more recent packages would have a larger set of dependencies.

ov_dt <- ov_dt |> 
  mutate(
    # Dependencies
    num_dep = purrr::map_int(
      .x = depends,
      .f = function(x){
        x |> 
          stringr::str_split(",", simplify = TRUE) |> 
          length()
      }
    ),
    num_dep = ifelse(is.na(depends), 0, num_dep),
    # Imports
    num_imports = purrr::map_int(
      .x = imports,
      .f = function(x){
        x |> 
          stringr::str_split(",", simplify = TRUE) |> 
          length()
      }
    ),
    num_imports = ifelse(is.na(imports), 0, num_imports)
  )
glimpse(ov_dt, 100)
Rows: 18,388
Columns: 17
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …

Authors

How many authors did the latest publish have? Perhaps this could provide some insights into if package authors are collaborating more than they used to. Is the R community working together?

ov_dt <- ov_dt |> 
  mutate(
    num_authors = purrr::map_int(
      .x = author,
      .f = function(x){
        x |> 
          stringr::str_split(",", simplify = TRUE) |> 
          length()
      }
    )
  )
glimpse(ov_dt, 100)
Rows: 18,388
Columns: 18
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …
$ num_authors       <int> 1, 2, 2, 2, 2, 5, 5, 5, 5, 3, 3, 3, 4, 5, 4, 2, 2, 3, 14, 4, 3, 1, 6, 6,…

Temporal

Temporal features typically useful for aggregation downstream.

hist_dt <- hist_dt |> 
  mutate(
    year = lubridate::year(date),
    month = lubridate::month(date, label = TRUE),
    day = lubridate::day(date),
    wday = lubridate::wday(date, label = TRUE),
    yr_mon = sprintf("%d-%s", year, month),
    dt = lubridate::ym(paste0(year, "-", month))
  )
ov_dt <- ov_dt |> 
  filter(!is.na(date_published)) |>
  mutate(
    year = lubridate::year(date_published),
    month = lubridate::month(date_published, label = TRUE),
    day = lubridate::day(date_published),
    wday = lubridate::wday(date_published, label = TRUE),
    yr_mon = sprintf("%d-%s", year, month),
    dt = lubridate::ym(paste0(year, "-", month))
  )
glimpse(ov_dt, 100)
Rows: 18,387
Columns: 24
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …
$ num_authors       <int> 1, 2, 2, 2, 2, 5, 5, 5, 5, 3, 3, 3, 4, 5, 4, 2, 2, 3, 14, 4, 3, 1, 6, 6,…
$ year              <dbl> 2015, 2020, 2019, 2021, 2019, 2022, 2015, 2016, 2019, 2017, 2022, 2017, …
$ month             <ord> Aug, Jun, Sep, Dec, Jun, May, May, Oct, Nov, Mar, May, Nov, Feb, May, Ju…
$ day               <int> 16, 14, 20, 14, 25, 19, 5, 20, 13, 13, 28, 6, 4, 28, 17, 3, 20, 30, 22, …
$ wday              <ord> Sun, Sun, Fri, Tue, Tue, Thu, Tue, Thu, Wed, Mon, Sat, Mon, Thu, Thu, Tu…
$ yr_mon            <chr> "2015-Aug", "2020-Jun", "2019-Sep", "2021-Dec", "2019-Jun", "2022-May", …
$ dt                <date> 2015-08-01, 2020-06-01, 2019-09-01, 2021-12-01, 2019-06-01, 2022-05-01,…

Titles & Descriptions

How long are the titles and description fields in the latest package submissions? Any interesting trends over time?

ov_dt <- ov_dt |>
  mutate(
    len_title = purrr::map_int(title, ~ stringr::str_count(.x, "\\w+")),
    len_desc = purrr::map_int(description, ~ stringr::str_count(.x, "\\w+"))
  )
glimpse(ov_dt, 100)
Rows: 18,387
Columns: 26
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …
$ num_authors       <int> 1, 2, 2, 2, 2, 5, 5, 5, 5, 3, 3, 3, 4, 5, 4, 2, 2, 3, 14, 4, 3, 1, 6, 6,…
$ year              <dbl> 2015, 2020, 2019, 2021, 2019, 2022, 2015, 2016, 2019, 2017, 2022, 2017, …
$ month             <ord> Aug, Jun, Sep, Dec, Jun, May, May, Oct, Nov, Mar, May, Nov, Feb, May, Ju…
$ day               <int> 16, 14, 20, 14, 25, 19, 5, 20, 13, 13, 28, 6, 4, 28, 17, 3, 20, 30, 22, …
$ wday              <ord> Sun, Sun, Fri, Tue, Tue, Thu, Tue, Thu, Wed, Mon, Sat, Mon, Thu, Thu, Tu…
$ yr_mon            <chr> "2015-Aug", "2020-Jun", "2019-Sep", "2021-Dec", "2019-Jun", "2022-May", …
$ dt                <date> 2015-08-01, 2020-06-01, 2019-09-01, 2021-12-01, 2019-06-01, 2022-05-01,…
$ len_title         <int> 9, 9, 8, 3, 8, 6, 8, 6, 9, 3, 5, 7, 7, 7, 4, 5, 5, 3, 4, 4, 5, 3, 8, 11,…
$ len_desc          <int> 28, 24, 40, 21, 52, 36, 11, 32, 41, 163, 19, 56, 33, 89, 12, 25, 88, 65,…

Package Licenses

The raw dataset has 159 unique levels for the license variable.

ov_dt |> 
  count(license) |> 
  reactable(compact = TRUE)

But many of being quite similar to each other, some binning is in order to extract some patterns. Here, I use the case_when to bin together similar licenses. (I’m no expert in these licenses. I’m sure I’m taking some liberties in the grouping here).

ov_dt <- ov_dt |> 
  mutate(
    license_cleaned = case_when(
      str_detect(license, "^GPL-3") ~ "GPL-3",
      str_detect(license, "^GPL\\s\\([\\s\\d\\.<=>]*3") ~ "GPL-3",
      str_detect(license, "^GPL-2") ~ "GPL-2",
      str_detect(license, "^GPL\\s\\([\\s\\d\\.<=>]*2") ~ "GPL-2",
      str_detect(license, "^AGPL") ~ "AGPL",
      str_detect(license, "^LGPL") ~ "LGPL",
      str_detect(license, "Apache") ~ "Apache",
      str_detect(license, "BSD") ~ "BSD",
      str_detect(license, "LGPL") ~ "LGPL",
      str_detect(license, "MIT") ~ "MIT",
      str_detect(license, "CC0") ~ "CC0",
      license == "GPL" ~ "GPL",
      TRUE ~ "Other"
      # str_detect(license, "GNU") ~ "GNU", # Left these out after some trials with plots below
      # str_detect(license, "MPL") ~ "MPL",
      # str_detect(license, "Unlimited") ~ "Unlimited",
      # str_detect(license, "^CC") ~ "CC",
      )
  )
glimpse(ov_dt, 100)
Rows: 18,387
Columns: 27
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …
$ num_authors       <int> 1, 2, 2, 2, 2, 5, 5, 5, 5, 3, 3, 3, 4, 5, 4, 2, 2, 3, 14, 4, 3, 1, 6, 6,…
$ year              <dbl> 2015, 2020, 2019, 2021, 2019, 2022, 2015, 2016, 2019, 2017, 2022, 2017, …
$ month             <ord> Aug, Jun, Sep, Dec, Jun, May, May, Oct, Nov, Mar, May, Nov, Feb, May, Ju…
$ day               <int> 16, 14, 20, 14, 25, 19, 5, 20, 13, 13, 28, 6, 4, 28, 17, 3, 20, 30, 22, …
$ wday              <ord> Sun, Sun, Fri, Tue, Tue, Thu, Tue, Thu, Wed, Mon, Sat, Mon, Thu, Thu, Tu…
$ yr_mon            <chr> "2015-Aug", "2020-Jun", "2019-Sep", "2021-Dec", "2019-Jun", "2022-May", …
$ dt                <date> 2015-08-01, 2020-06-01, 2019-09-01, 2021-12-01, 2019-06-01, 2022-05-01,…
$ len_title         <int> 9, 9, 8, 3, 8, 6, 8, 6, 9, 3, 5, 7, 7, 7, 4, 5, 5, 3, 4, 4, 5, 3, 8, 11,…
$ len_desc          <int> 28, 24, 40, 21, 52, 36, 11, 32, 41, 163, 19, 56, 33, 89, 12, 25, 88, 65,…
$ license_cleaned   <chr> "GPL-2", "GPL-3", "GPL-3", "GPL-3", "MIT", "GPL-3", "GPL-3", "GPL-3", "G…

Domains

Which domains do package authors typically use? My guess is GitHub rules them all, but is that true? Can we see any rise of other offerings like GitLab or BitBucket?

ov_dt <- ov_dt |>
  mutate(url_domain = map_chr(url,
                              ~ {
                                if (is.na(.x))
                                  return(NA)
                                else
                                  return(url_parse(.x)$domain)
                              }),
         bug_domain = map_chr(bug_reports,
                              ~ {
                                if (is.na(.x))
                                  return(NA)
                                else
                                  return(url_parse(.x)$domain)
                              }))
glimpse(ov_dt, 100)
Rows: 18,387
Columns: 29
$ package           <chr> "A3", "AATtools", "ABACUS", "abbreviate", "abbyyR", "abc", "abc.data", "…
$ version           <chr> "1.0.0", "0.0.1", "1.0.0", "0.1", "0.5.5", "2.2.1", "1.0", "0.9.0", "1.0…
$ major             <chr> "1", "0", "1", "0", "0", "2", "1", "0", "1", "1", "0", "0", "1", "1", "1…
$ minor             <chr> "0", "0", "0", "1", "5", "2", "0", "9", "0", "2", "3", "15", "2", "0", "…
$ patch             <chr> "0", "1", "0", NA, "5", "1", NA, "0", NA, "1", "0", "0", NA, "3", "3", N…
$ depends           <chr> "R (>= 2.15.0), xtable, pbapply", "R (>= 3.6.0)", "R (>= 3.1.0)", NA, "R…
$ imports           <chr> NA, "magrittr, dplyr, doParallel, foreach", "ggplot2 (>= 3.1.0), shiny (…
$ license           <chr> "GPL (>= 2)", "GPL-3", "GPL-3", "GPL-3", "MIT + file LICENSE", "GPL (>= …
$ needs_compilation <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, TRU…
$ author            <chr> "Scott Fortmann-Roe", "Sercan Kahveci [aut, cre]", "Mintu Nath [aut, cre…
$ bug_reports       <chr> NA, "https://github.com/Spiritspeak/AATtools/issues", NA, NA, "http://gi…
$ url               <chr> NA, NA, "https://shiny.abdn.ac.uk/Stats/apps/", "https://github.com/sigb…
$ date_published    <date> 2015-08-16, 2020-06-14, 2019-09-20, 2021-12-14, 2019-06-25, 2022-05-19,…
$ description       <chr> "Supplies tools for tabulating and analyzing the results of predictive m…
$ title             <chr> "Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModel…
$ num_dep           <dbl> 3, 1, 1, 0, 1, 6, 1, 1, 0, 1, 1, 0, 1, 0, 6, 5, 0, 0, 1, 0, 0, 1, 1, 1, …
$ num_imports       <dbl> 0, 4, 3, 0, 6, 0, 0, 3, 1, 1, 2, 4, 0, 1, 0, 0, 1, 0, 4, 4, 3, 2, 0, 8, …
$ num_authors       <int> 1, 2, 2, 2, 2, 5, 5, 5, 5, 3, 3, 3, 4, 5, 4, 2, 2, 3, 14, 4, 3, 1, 6, 6,…
$ year              <dbl> 2015, 2020, 2019, 2021, 2019, 2022, 2015, 2016, 2019, 2017, 2022, 2017, …
$ month             <ord> Aug, Jun, Sep, Dec, Jun, May, May, Oct, Nov, Mar, May, Nov, Feb, May, Ju…
$ day               <int> 16, 14, 20, 14, 25, 19, 5, 20, 13, 13, 28, 6, 4, 28, 17, 3, 20, 30, 22, …
$ wday              <ord> Sun, Sun, Fri, Tue, Tue, Thu, Tue, Thu, Wed, Mon, Sat, Mon, Thu, Thu, Tu…
$ yr_mon            <chr> "2015-Aug", "2020-Jun", "2019-Sep", "2021-Dec", "2019-Jun", "2022-May", …
$ dt                <date> 2015-08-01, 2020-06-01, 2019-09-01, 2021-12-01, 2019-06-01, 2022-05-01,…
$ len_title         <int> 9, 9, 8, 3, 8, 6, 8, 6, 9, 3, 5, 7, 7, 7, 4, 5, 5, 3, 4, 4, 5, 3, 8, 11,…
$ len_desc          <int> 28, 24, 40, 21, 52, 36, 11, 32, 41, 163, 19, 56, 33, 89, 12, 25, 88, 65,…
$ license_cleaned   <chr> "GPL-2", "GPL-3", "GPL-3", "GPL-3", "MIT", "GPL-3", "GPL-3", "GPL-3", "G…
$ url_domain        <chr> NA, NA, "shiny.abdn.ac.uk", "github.com", "github.com", NA, NA, NA, NA, …
$ bug_domain        <chr> NA, "github.com", NA, NA, "github.com", NA, NA, NA, NA, NA, "github.com"…

Graphical EDA

Now that I have the data sets prepared and ready, it’s time for the fun part - being creative and creating some interesting visuals! Let’s attack those questions one at a time.

  1. How long did packages take from their first release to version 1.0?
  2. Which packages have had the most version updates?
  3. What type of packages were most frequent in different years?
  4. Who are the most productive authors?
  5. Can you predict the growth toward 2025?
  6. What license is most used? Has there been a change over time?
  7. How many packages use all CAPS, all small, or a mixture?
  8. Which repositories do packages use? Github/Bitbucket etc. How do these vary over time?
  9. Do packages have URLs for bug reports?
  10. Is there any temporal patterns to when versions are submitted to CRAN?
  11. Have titles & descriptions gotten longer over time?
  12. Do authors use minor versions?

Package Dependencies

Q: How have the dependencies & imports changed over time?

Taking the last published year-month combo for the active packages in the repo, I can calculate the median values for dependencies and imports. Medians will be robust against outliers, while also conviniently giving us whole numbers.

deps <- ov_dt |> 
  select(year, len_desc, len_title) |> 
  arrange(-year) |> 
  filter(!is.na(year), year > 2008) |> 
  mutate(year = factor(year, levels = seq(2008, 2022)))
deps |>
  pivot_longer(-year) |> 
  ggplot(aes(y = year, x = value, fill = name)) +
  stat_density_ridges(
    bandwidth = 4,
    jittered_points = F,
    position = position_points_jitter(height = 0),
                      point_shape = "|",
                      point_size = 2,
                      size = 0.25,
                      scale = .95,
                      quantile_lines = TRUE, 
                      quantiles = 2,
                      alpha = 0.7, 
                      rel_min_height = 0.01) +
  scale_x_continuous(limits = c(0,200), expand = c(0,0)) +
  coord_cartesian(clip = "off") +
  theme_ridges(center = TRUE)

  • Have titles & descriptions gotten longer over time?
ov_dt |> 
    group_by(dt) |> 
    summarise_at(vars(len_title, len_desc), list(median = median, sd = sd), na.rm = TRUE) |> 
  ggplot(aes(x= dt)) +
    geom_jitter(aes(y = len_title_median, color = "len_title_median"), alpha = 0.2) +
  geom_smooth(aes(y = len_title_median, color = "len_title_median"), span = 0.3, se = FALSE) +
  geom_jitter(aes(y = len_desc_median, color = "len_desc_median"), alpha = 0.2) +
  geom_smooth(aes(y = len_desc_median, color = "len_desc_median"), span = 0.3, se = FALSE)

  # theme_light()
ov_dt |> 
  filter(year %in% c(2022, 2020, 2018)) |> 
  ggplot() +
  geom_density(aes(x = len_desc, 
                   fill = as.factor(year), 
                   color = as.factor(year)
                   ),
               alpha = 0.3
               )

ov_dt |> 
  filter(year %in% c(2022, 2020, 2018)) |> 
  ggplot() +
  geom_histogram(aes(x = len_desc, 
                   fill = as.factor(year), 
                   color = as.factor(year)
                   ),
               alpha = 0.3
               ) +
  facet_wrap(~year)

ov_dt |> 
  ggplot(aes(x= date_published, y = len_title)) +
  geom_jitter(alpha = 0.05) +
  geom_smooth(span = 0.1, se = FALSE) +
  theme_light() +
  scale_y_log10()


ov_dt |> 
  ggplot(aes(x= date_published, y = len_desc)) +
  geom_jitter(alpha = 0.05) +
  geom_smooth(span = 0.2, se = FALSE) +
  theme_light() +
  scale_y_log10()

  • What license is most used? Has there been a change over time?
ov_dt |> 
  group_by(license_cleaned) |> 
  count() |> 
  ggplot(aes(x = forcats::fct_reorder(license_cleaned, n), y = n, fill = license_cleaned)) +
  geom_col() +
  coord_flip() +
  theme_minimal() +
  guides(fill = FALSE) +
  labs(x = "", y = "")
Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

plot_bubbles <- function(dat,
                         .scale,
                         plot_radius,
                         bubble_radius,
                         alpha,
                         maxiter) {
  .qty <- nrow(dat)
  
  theta <- seq(0, 360, length.out = .qty + 1)
  
  dat$x <- plot_radius * cos(theta * pi / 180)[-1]
  dat$y <- plot_radius * sin(theta * pi / 180)[-1]
  dat$n_scaled <- dat$n / .scale
  
  xpack <- rep(dat$x, times = dat$n_scaled)
  ypack <- rep(dat$y, times = dat$n_scaled)
  
  coords <- tibble(
    x = xpack + runif(length(xpack)),
    y = ypack + runif(length(ypack)),
    r = bubble_radius
  )
  
  packed_coords <-
    circleRepelLayout(coords, sizetype = "r", maxiter = maxiter)
  
  packed_coords$layout |>
    ggplot(aes(x, y)) +
    geom_point(aes(size = radius), alpha = alpha) +
    coord_equal() +
    theme_minimal() +
    theme(
      legend.position = "none",
      panel.grid = element_blank(),
      axis.title = element_blank(),
      axis.text = element_blank()
    ) +
    geom_text(
      aes(
        x = x,
        y = y,
        label = label
      ),
      data = dat,
      hjust = "center",
      vjust = "center"
    )
}

ov_dt |> 
    count(license_cleaned) |> 
    top_n(6, n) |> 
  mutate(label = sprintf("%s\n%d Pkgs", license_cleaned, n)) |> 
    arrange(runif(1:n())) |> 
  plot_bubbles(
    .scale = 100,
    plot_radius = 10,
    bubble_radius = 0.56,
    alpha = 0.2,
    maxiter = 1000
  )



# .scale <- 100
# .qty <- 6
# lic <- ov_dt |> 
#   group_by(license_cleaned) |> 
#   count() |> 
#   arrange(-n) |> 
#   head(.qty) |>
#   mutate(n_scaled = round(n / .scale)) |> 
#   arrange(runif(1:n()))
#   
# r <- 10
# theta <- seq(0, 360, length.out = .qty+1)
# lic$x <- r * cos(theta * pi / 180)[-1]
# lic$y <- r * sin(theta * pi / 180)[-1]
# xpack <- rep(lic$x, times=lic$n_scaled)
# ypack <- rep(lic$y, times=lic$n_scaled)
# 
# coords <- tibble(x=xpack+runif(length(xpack)),
#                      y=ypack+runif(length(ypack)),
#                      r=.56)
# packed_coords <- circleRepelLayout(coords, sizetype="r", maxiter=1000)
# packed_coords$layout |> 
#   ggplot(aes(x, y)) +
#   geom_point(aes(size = radius), alpha = 0.2) +
#   coord_equal() +
#   theme_minimal() +
#   theme(legend.position = "none",
#         panel.grid = element_blank(),
#         axis.title = element_blank(),
#         axis.text = element_blank()) +
#   geom_text(aes(x = x, 
#                 y = y, 
#                 label = sprintf("%s\n%d Pkgs", license_cleaned, n)),
#             data = lic,
#             hjust = "center", 
#             vjust = "center") 
ov_dt |> 
  group_by(dt) |> 
  count(license_cleaned) |> 
  mutate(license_cleaned = forcats::fct_reorder(license_cleaned, n)) |> 
  ggplot(aes(x= dt, y = n, color = license_cleaned)) +
  # geom_line( alpha = 0.3) +
  geom_jitter(alpha = 0.3) +
  geom_smooth(span = 0.3, se = FALSE) +
  theme_light()

  • Do packages have URLs for bug reports?
ov_dt |> 
  group_by(dt) |> 
  count(url_exist = is.na(url)) |>  
  ggplot(aes(x= dt, y = n, color = url_exist)) +
  geom_jitter(alpha = 0.3) +
  geom_smooth(span = 0.3, se = FALSE) +
  theme_light()

ov_dt |> 
  group_by(dt) |> 
  count(url_exist = is.na(bug_reports)) |>  
  ggplot(aes(x= dt, y = n, color = url_exist)) +
  geom_jitter(alpha = 0.3) +
  geom_smooth(span = 0.3, se = FALSE) +
  theme_light()

  • Which repositories do packages use? Github/Bitbucket etc. How do these vary over time?
ov_dt |> 
  filter(domain != "") |> 
  mutate(domain = forcats::fct_lump_min(domain, 20)) |> 
  group_by(dt) |> 
  count(domain) |>  
  ggplot(aes(x= dt, y = n, color = domain)) +
  geom_jitter(alpha = 0.3) +
  geom_smooth(span = 0.5, se = FALSE) +
  theme_light()

ov_dt |> 
   filter(domain != "") |> 
  mutate(domain = forcats::fct_lump_min(domain, 20)) |> 
    count(domain) |> 
  mutate(label = sprintf("%s\n%d", domain, n)) |> 
    arrange(runif(1:n())) |> 
  plot_bubbles(
    .scale =50,
    plot_radius = 6,
    bubble_radius = 0.4,
    alpha = 0.2,
    maxiter = 1000
  )

  • Is there any temporal patterns to when versions are submitted to CRAN?
ov_dt |> 
  filter(!is.na(dt), dt < "2022-07-01") |> 
  count(dt) |> 
  arrange(dt) |> 
  timetk::pad_by_time(dt, .by = "month", .pad_value = 0) |> 
  ggplot(aes(dt, n)) +
  geom_line()


ov_dt |> 
  filter(!is.na(dt), dt < "2022-07-01") |> 
  count(dt) |> 
  arrange(dt) |> 
  timetk::pad_by_time(dt, .by = "month", .pad_value = 0) -> xdat
timetk::plot_seasonal_diagnostics(xdat, dt, log(n), .interactive = FALSE)


timetk::plot_stl_diagnostics(xdat |> filter(dt > "2018-01-01", dt < "2022-07-01"), dt, n, .interactive = FALSE, .feature_set = c("observed", "season", "trend", "remainder"))
frequency = 12 observations per 1 year
trend = 12 observations per 1 year

LS0tCnRpdGxlOiAiQ1JBTiBIaXN0b3J5IEVEQSIKYXV0aG9yOiAiUiBTYW5nb2xlIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIGhpZ2hsaWdodDoga2F0ZQogICAgdGhlbWU6IHBhcGVyCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgojIEludHJvZHVjdGlvbgoKV2VsY29tZSB0byB0aGUgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyBvZiB0aGUgW0NSQU5dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnKSBoaXN0b3JpY2FsIGRhdGEgc2V0LiBBcyB5b3UgbWF5IGFscmVhZHkga25vdywgQ1JBTiBpcyBhIG5ldHdvcmsgb2Ygc2VydmVycyBhcm91bmQgdGhlIHdvcmxkIHdoaWNoIHN0b3JlIGNvZGUgYW5kIGRvY3VtZW50YXRpb24gZm9yIHRoZSBSIHBhY2thZ2VzIG92ZXIgdGltZS4gQXMgb2Ygd3JpdGluZyB0aGlzIEVEQSwgQ1JBTiBoYWQganVzdCBvdmVyIDE4LDAwMCBwYWNrYWdlcyBhdmFpbGFibGUgaW4gaXQncyByZXBvc2l0b3J5LgoKWyFbQ1JBTiBXZWJzaXRlXShpbWFnZXMvcGFzdGUtNzE1QUVDODMucG5nKXt3aWR0aD0iNjkzIn1dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnKQoKW0hlYWRzIG9yIFRhaWxzXShodHRwczovL3d3dy5rYWdnbGUuY29tL2hlYWRzb3J0YWlscykgaGFzIGRvbmUgYSBncmVhdCBqb2Igb2YgZ3JhYmJpbmcgaGlzdG9yaWNhbCBkYXRhLCBjbGVhbmluZyBpdCB1cCBhbmQgcHJlcGFyaW5nIGl0IGZvciB1cyBSIGVudGh1c2lhc3RzLiBSZWFkIGFib3V0IHRoZSBhcHByb2FjaCBoZSBmb2xsb3dlZCBpbiBoaXMgW2Jsb2dwb3N0XShodHRwczovL2hlYWRzMHJ0YWkxcy5naXRodWIuaW8vMjAyMi8wNy8yMi9rYWdnbGUtZGF0YXNldC1jcmFuLXBhY2thZ2VzLykuCgojIEluaXRpYWwgU2V0dXAgey50YWJzZXR9CgpfUmVhZCB0aHJvdWdoIHRoZSBpbml0aWFsIHNldHVwIGluIHRoZSA0IHRhYnMgYmVsb3cuXwoKIyMgTGlicmFyaWVzIHsudGFic2V0fQoKRmlyc3QsIHNvbWUgSSBpbXBvcnQgc29tZSB1c2VmdWwgbGlicmFyaWVzIGFuZCBzZXQgc29tZSBwbG90dGluZyBkZWZhdWx0cy4KCmBgYHtyIGxpYnJhcmllcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBEYXRhIE1hbmlwdWxhdGlvbgpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHNraW1yKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkodXJsdG9vbHMpCgojIFBsb3RzCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShuYW5pYXIpCmxpYnJhcnkocGFja2NpcmNsZXMpCmxpYnJhcnkoZ2dyaWRnZXMpCgojIFRhYmxlcwpsaWJyYXJ5KHJlYWN0YWJsZSkKCiMgU2V0dGluZ3MKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoCiAgYmFzZV9zaXplID0gMTQsCiAgYmFzZV9mYW1pbHkgPSAiTWVubG8iLAogIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIKKSkKYGBgCgoKIyMgUmVhZCBJbiB7LnRhYnNldH0KCkxldCdzIHN0YXJ0IGJlIHJlYWRpbmcgaW4gdGhlIGRhdGEuIFRoZXJlIGFyZSB0d28gYENTVmAgZmlsZXMgaW4gdGhpcyBkYXRhc2V0LiBGcm9tIGhpcyBbZGF0YXNldCBwYWdlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL2hlYWRzb3J0YWlscy9yLXBhY2thZ2UtaGlzdG9yeS1vbi1jcmFuKToKCi0gICBgY3Jhbl9wYWNrYWdlX292ZXJ2aWV3LmNzdmA6IGFsbCBSIHBhY2thZ2VzIGN1cnJlbnRseSBhdmFpbGFibGUgdGhyb3VnaCBDUkFOLCB3aXRoICh1c3VhbGx5KSAxIHJvdyBwZXIgcGFja2FnZS4uLgotICAgYGNyYW5fcGFja2FnZV9oaXN0b3J5LmNzdmA6IHZlcnNpb24gaGlzdG9yeSBvZiB2aXJ0dWFsbHkgYWxsIHBhY2thZ2VzIGluIHRoZSBwcmV2aW91cyB0YWJsZVwuLi4KCgpgYGB7cn0KaGlzdF9kdCA8LSByZWFkX2NzdigKICAiLi4vaW5wdXQvY3Jhbl9wYWNrYWdlX2hpc3RvcnkuY3N2IiwKICBjb2xfdHlwZXMgPSBjb2xzKAogICAgcGFja2FnZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIHZlcnNpb24gPSBjb2xfY2hhcmFjdGVyKCksCiAgICBkYXRlID0gY29sX2RhdGUoZm9ybWF0ID0gIiVZLSVtLSVkIiksCiAgICByZXBvc2l0b3J5ID0gY29sX2NoYXJhY3RlcigpCiAgKQopCm92X2R0IDwtIHJlYWRfY3N2KAogICIuLi9pbnB1dC9jcmFuX3BhY2thZ2Vfb3ZlcnZpZXcuY3N2IiwKICBjb2xfdHlwZXMgPSBjb2xzKAogICAgcGFja2FnZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIHZlcnNpb24gPSBjb2xfY2hhcmFjdGVyKCksCiAgICBkZXBlbmRzID0gY29sX2NoYXJhY3RlcigpLAogICAgaW1wb3J0cyA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIGxpY2Vuc2UgPSBjb2xfY2hhcmFjdGVyKCksCiAgICBuZWVkc19jb21waWxhdGlvbiA9IGNvbF9sb2dpY2FsKCksCiAgICBhdXRob3IgPSBjb2xfY2hhcmFjdGVyKCksCiAgICBidWdfcmVwb3J0cyA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIHVybCA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIGRhdGVfcHVibGlzaGVkID0gY29sX2RhdGUoZm9ybWF0ID0gIiVZLSVtLSVkIiksCiAgICBkZXNjcmlwdGlvbiA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgIHRpdGxlID0gY29sX2NoYXJhY3RlcigpCiAgKQopCmBgYAoKIyMgUXVpY2sgVmlldyB7LnRhYnNldH0KCkkgbG92ZSB0byB0YWtlIHRoZSBmaXJzdCBwZWVrIGludG8gYSBkYXRhc2V0IHdpdGggdGhlIGFtYXppbmcgW2B7c2tpbXJ9YF0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9za2ltci9pbmRleC5odG1sKSBwYWNrYWdlLiBXZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSB0aGUgcmlnaHQgZGF0YSB0eXBlcyBzZXQgZm9yIGFsbCB0aGUgY29sdW1ucywgZGF0ZXMgaGF2ZSBiZWVuIGltcG9ydGVkIGNvcnJlY3RseS4gCgpXZSBjYW4gc2VlIGluIHRoZSBgaGlzdG9yeWAgZGF0YSB0aGF0IHRoZSAxc3QgcGFja2FnZSByZXBvcnRlZCBvbiBDUkFOIHdhcyBpbiAyMiB5ZWFycyBhZ28gb24gX2ByIG1pbihoaXN0X2R0JGRhdGUpYCFfICBGdXJ0aGVybW9yZSwgdGhlIGBvdmVydmlld2AgdGVsbHMgdXMgdGhlcmUncyBhIHBhY2thZ2UgX3tgciBvdl9kdCRwYWNrYWdlW292X2R0JGRhdGVfcHVibGlzaGVkID09IG1pbihvdl9kdCRkYXRlX3B1Ymxpc2hlZCwgbmEucm0gPSBUKV1bWzFdXWB9XyBsYXN0IHB1Ymxpc2hlZC91cGRhdGVkIG9uIGByIG1pbihvdl9kdCRkYXRlX3B1Ymxpc2hlZCwgbmEucm0gPSBUUlVFKWAuCgpXaGlsZSB0aGVyZSdzIG5vIG1pc3NpbmcgZGF0YSBpbiB0aGUgYGhpc3RvcnlgIGRhdGFzZXQsIHRoZXJlIGFyZSBhIGJ1bmNoIG9mIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBgb3ZlcnZpZXdgIGRhdGFzZXQuIExldCdzIGV4cGxvcmUgdGhpcyBhIGJpdCBtb3JlLgoKYGBge3IgcGFnZWQucHJpbnQ9RkFMU0V9CnNraW1yOjpza2ltKGhpc3RfZHQpCnNraW1yOjpza2ltKG92X2R0KQpgYGAKCiMjIERhdGEgUXVhbGl0eSB7LnRhYnNldH0KCk15IGZhdm9yaXRlIHdheSBvZiBleHBsb3JpbmcgbWlzc2luZyBkYXRhIGlzIHRvIG1ha2UgaXQgdmlzaWJsZSwgdXNpbmcgW05pY2sgVGllcm5leSdzXShodHRwczovL3d3dy5uanRpZXJuZXkuY29tL2Fib3V0LykgYW1hemluZyBbYHtuYW5pYXJ9YF0oaHR0cHM6Ly9naXRodWIuY29tL25qdGllcm5leS9uYW5pYXIpIHBhY2thZ2UuIFRoZXJlIGFyZSBhIGZldyBjb2x1bW5zIHdpdGggbWlzc2luZyBkYXRhLiBMZXQncyBsb29rIGF0IHRoZXNlIG1vcmUgY2xvc2VseS4KCiogYGRlcGVuZHNgIGFuZCBgaW1wb3J0c2AgaGF2ZSByb3VnaGx5IGEgcXVhcnRlciBvZiB0aGUgZGF0YSBhcyBgPE5BPmAuIFRoZXNlIGFyZSBwYWNrYWdlcyB3aGljaCBfcm91Z2hseV8gaGF2ZSBubyBleHRlcm5hbCBkZXBlbmRlbmNpZXMuIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBjYW4gZ2V0IGEgYml0IGNvbXBsZXg7IGJlc3QgdG8gbGVhcm4gYWJvdXQgaXQgaW4gSGFkbGV5J3MgY2hhcHRlciBbaGVyZV0oaHR0cHM6Ly9yLXBrZ3Mub3JnL2RlcGVuZGVuY2llcy5odG1sKS4KKiBSb3VnaGx5IGhhbGYgb2YgYGJ1Z19yZXBvcnRzYCBhbmQgYHVybGAgYXJlIG1pc3NpbmcuIFRoZXNlIGRvbid0IHNlZW0gdG8gYmUgZGF0YSBpc3N1ZXMgYXMgbXVjaCBhcyBhdXRob3JzIHdobyBkb24ndCBoYXZlIGEgcGxhY2UgdG8gaXNzdWUgYnVncyBvciB3ZWJzaXRlIGZvciB0aGVpciBwYWNrYWdlIHJlc3BlY3RpdmVseS4KKiBgZGF0ZV9wdWJsaXNoZWRgIGhhcyBvbmx5IDEgcm93IHdpdGggbWlzc2luZywgd2hpY2ggc2VlbXMgbGlrZSBhIGRhdGEgcXVhbGl0eSBzcGlsbC4KCmBgYHtyfQpvdl9kdCB8PiAKICBkcGx5cjo6YXJyYW5nZShkYXRlX3B1Ymxpc2hlZCkgfD4gCiAgdmlzX21pc3MoKQpgYGAKCgojIEludGVyZXN0aW5nIFF1ZXN0aW9ucwoKU2luY2UgdGhpcyBpcyBhbiBvcGVuIGVuZGVkIGV4cGxvcmF0aW9uIC0gdW5saWtlIG90aGVyIEVEQSB3aXRoIHRoZSBwdXJwb3NlIG9mIGJ1aWxkaW5nIGEgcHJlZGljdGl2ZSBtb2RlbCAtIGJlZm9yZSBJIGNvbnRpbnVlIHRvIHRoZSBwbG90dGluZywgSSdkIGxpa2UgdG8gcG9zaXQgc29tZSBxdWVzdGlvbnMgd2hpY2ggd2lsbCBndWlkZSB0aGUgZmxvdyBvZiBmdXJ0aGVyIHdvcmsuIFRoZSBmaXJzdCBmaXZlIHF1ZXN0aW9ucyBhcmUgZnJvbSBbTWFydGluJ3MgYmxvZ10oaHR0cHM6Ly9oZWFkczBydGFpMXMuZ2l0aHViLmlvLzIwMjIvMDcvMjIva2FnZ2xlLWRhdGFzZXQtY3Jhbi1wYWNrYWdlcy8pLCB3aXRoIGZ1cnRoZXIgcXVlc3Rpb25zIHdoaWNoIEkgdGhpbmsgd291bGQgYmUgaW50ZXJlc3RpbmcgdG8gZXhwbG9yZS4KCjEuIEhvdyBsb25nIGRpZCBwYWNrYWdlcyB0YWtlIGZyb20gdGhlaXIgZmlyc3QgcmVsZWFzZSB0byB2ZXJzaW9uIDEuMD8KMS4gV2hpY2ggcGFja2FnZXMgaGF2ZSBoYWQgdGhlIG1vc3QgdmVyc2lvbiB1cGRhdGVzPwoxLiBXaGF0IHR5cGUgb2YgcGFja2FnZXMgd2VyZSBtb3N0IGZyZXF1ZW50IGluIGRpZmZlcmVudCB5ZWFycz8KMS4gV2hvIGFyZSB0aGUgbW9zdCBwcm9kdWN0aXZlIGF1dGhvcnM/CjEuIENhbiB5b3UgcHJlZGljdCB0aGUgZ3Jvd3RoIHRvd2FyZCAyMDI1PwoxLiBXaGF0IGxpY2Vuc2UgaXMgbW9zdCB1c2VkPyBIYXMgdGhlcmUgYmVlbiBhIGNoYW5nZSBvdmVyIHRpbWU/CjEuIEhvdyBtYW55IHBhY2thZ2VzIHVzZSBhbGwgQ0FQUywgYWxsIHNtYWxsLCBvciBhIG1peHR1cmU/CjEuIEhvdyBoYXZlIHRoZSBkZXBlbmRlbmNpZXMgJiBpbXBvcnRzIGNoYW5nZWQgb3ZlciB0aW1lPwoxLiBXaGljaCByZXBvc2l0b3JpZXMgZG8gcGFja2FnZXMgdXNlPyBHaXRodWIvQml0YnVja2V0IGV0Yy4gSG93IGRvIHRoZXNlIHZhcnkgb3ZlciB0aW1lPwoxLiBEbyBwYWNrYWdlcyBoYXZlIFVSTHMgZm9yIGJ1ZyByZXBvcnRzPwoxLiBJcyB0aGVyZSBhbnkgdGVtcG9yYWwgcGF0dGVybnMgdG8gd2hlbiB2ZXJzaW9ucyBhcmUgc3VibWl0dGVkIHRvIENSQU4/CjEuIEhhdmUgdGl0bGVzICYgZGVzY3JpcHRpb25zIGdvdHRlbiBsb25nZXIgb3ZlciB0aW1lPwoxLiBEbyBhdXRob3JzIHVzZSBtaW5vciB2ZXJzaW9ucz8KCgojIEZlYXR1cmUgRGV2ZWxvcG1lbnQgey50YWJzZXR9CgpUbyBhaWQgYW5zd2VyaW5nIG1hbnkgb2YgdGhlc2UsIEkgZmlyc3QgbmVlZCB0byBjcmVhdGUgYSBmZXcgbmV3IGZlYXR1cmVzIGluIHRoZSBgb3ZlcnZpZXdgIGRhdGEgc2V0LiAKCl9SZWFkIGFib3V0IHRoZSBmZWF0dXJlIGRldmVsb3BtZW50IGluIHRoZSB0YWJzIGJlbG93LiBXZSBnbyBmcm9tIDEyIGNvbHVtbnMgdG8gMjkgY29sdW1ucyBpbiB0aGUgYG92ZXJ2aWV3YCBkYXRhIHNldC5fCgojIyBWZXJzaW9uIE51bWJlcnMgey50YWJzZXR9CgpQZXIgdGhlIFIgcGFja2FnZSBzZWN0aW9uIGluIFtIYWRsZXkncyBib29rXShodHRwczovL3ItcGtncy5vcmcvbGlmZWN5Y2xlLmh0bWwjdmVyc2lvbiksIF8iYW4gUiBwYWNrYWdlIHZlcnNpb24gaXMgYSBzZXF1ZW5jZSBvZiBhdCBsZWFzdCB0d28gaW50ZWdlcnMgc2VwYXJhdGVkIGJ5IGVpdGhlciBgLmAgb3IgYC1gLiBGb3IgZXhhbXBsZSwgYDEuMGAgYW5kIGAwLjkuMS0xMGAgYXJlIHZhbGlkIHZlcnNpb25zLCBidXQgYDFgIGFuZCBgMS4wLWRldmVsYCBhcmUgbm90Il8uIFR5cGljYWxseSwgcGFja2FnZXMgZG8gZm9sbG93IHRoZSB0aHJlZSBudW1iZXIgZm9ybWF0IG9mIGA8bWFqb3I+LjxtaW5vcj4uPHBhdGNoPmAuIEknbSBtYWtpbmcgYW4gYXNzdW1wdGlvbiB0aGlzIGlzIHRydWUsIGp1c3QgdG8gc2ltcGxpZnkgdGhpbmdzLiBJIGhhdmUgYSBmZWVsaW5nIGl0J2xsIGNhcHR1cmUgbW9zdCBvZiB0aGUgY2FzZXMuIAoKVGhpcyBmZWF0dXJlIGNvdWxkIGhlbHAgYW5zd2VyIHF1ZXN0aW9ucyBhYm91dCB2ZXJzaW9uIG51bWJlciBwcm9ncmVzc2lvbnMuCgpgYGB7cn0Kc3BsaXRfdmVyc2lvbnMgPC0gZnVuY3Rpb24oZGF0KSB7CiAgc3RvcGlmbm90KCJ2ZXJzaW9uIiAlaW4lIG5hbWVzKGRhdCkpCiAgCiAgZGF0IHw+CiAgICBzZXBhcmF0ZSgKICAgICAgdmVyc2lvbiwKICAgICAgaW50byA9CiAgICAgICAgYygibWFqb3IiLCAibWlub3IiLCAicGF0Y2giKSwKICAgICAgc2VwID0gIlxcLiIsCiAgICAgIGV4dHJhID0gIm1lcmdlIiwgIyBmb3IgdmVyc2lvbnMgbGlrZSAxLjAuMy0zMDAwLCBrZWVwIHRoZSAnMy0zMDAwJyB0b2dldGhlciBpbiB0aGUgM3JkIGNvbAogICAgICBmaWxsID0gInJpZ2h0IiwKICAgICAgcmVtb3ZlID0gRkFMU0UKICAgICkKfQoKb3ZfZHQgPC0gb3ZfZHQgfD4gc3BsaXRfdmVyc2lvbnMoKQpoaXN0X2R0IDwtIGhpc3RfZHQgfD4gc3BsaXRfdmVyc2lvbnMoKQpoZWFkKGhpc3RfZHQpIHw+IAogIHJlYWN0YWJsZShjb21wYWN0ID0gVFJVRSkKYGBgCgojIyBEZXBlbmRpZXMgJiBJbXBvcnRzIHsudGFic2V0fQoKRm9yIHRoZSBsYXN0IHB1Ymxpc2hlZCB2ZXJzaW9uIG9mIHRoZSBwYWNrYWdlLCBob3cgbWFueSBkZXBlbmRlbmNpZXMgYW5kL2FuZCBpbXBvcnRzIGRvZXMgZWFjaCBwYWNrYWdlIGhhdmU/IE15IGh5cG90aGVzaXMgaXMgdGhhdCBwYWNrYWdlcyBpbiB0aGUgcGFzdCByZWxpZWQgb24gbGVzc2VyIGRlcGVuZGVuY2llcyBzaW5jZSB0aGV5IHdlcmUgbW9yZSBsaWtlbHkgdGhhbiBub3Qgd3JpdHRlbiBpbiBiYXNlIFIuIFdpdGggdGhlIHJlY2VudCBleHBsb3Npb24gb2YgYWRvcHRpb24gb2YgUiwgYW5kIHRoZSBhZG9wdGlvbiBvZiB0aGUgdGlkeXZlcnNlIGZyYW1ld29yaywgbW9yZSByZWNlbnQgcGFja2FnZXMgd291bGQgaGF2ZSBhIGxhcmdlciBzZXQgb2YgZGVwZW5kZW5jaWVzLgoKYGBge3J9Cm92X2R0IDwtIG92X2R0IHw+IAogIG11dGF0ZSgKICAgICMgRGVwZW5kZW5jaWVzCiAgICBudW1fZGVwID0gcHVycnI6Om1hcF9pbnQoCiAgICAgIC54ID0gZGVwZW5kcywKICAgICAgLmYgPSBmdW5jdGlvbih4KXsKICAgICAgICB4IHw+IAogICAgICAgICAgc3RyaW5ncjo6c3RyX3NwbGl0KCIsIiwgc2ltcGxpZnkgPSBUUlVFKSB8PiAKICAgICAgICAgIGxlbmd0aCgpCiAgICAgIH0KICAgICksCiAgICBudW1fZGVwID0gaWZlbHNlKGlzLm5hKGRlcGVuZHMpLCAwLCBudW1fZGVwKSwKICAgICMgSW1wb3J0cwogICAgbnVtX2ltcG9ydHMgPSBwdXJycjo6bWFwX2ludCgKICAgICAgLnggPSBpbXBvcnRzLAogICAgICAuZiA9IGZ1bmN0aW9uKHgpewogICAgICAgIHggfD4gCiAgICAgICAgICBzdHJpbmdyOjpzdHJfc3BsaXQoIiwiLCBzaW1wbGlmeSA9IFRSVUUpIHw+IAogICAgICAgICAgbGVuZ3RoKCkKICAgICAgfQogICAgKSwKICAgIG51bV9pbXBvcnRzID0gaWZlbHNlKGlzLm5hKGltcG9ydHMpLCAwLCBudW1faW1wb3J0cykKICApCmdsaW1wc2Uob3ZfZHQsIDEwMCkKYGBgCgojIyBBdXRob3JzIHsudGFic2V0fQoKSG93IG1hbnkgYXV0aG9ycyBkaWQgdGhlIGxhdGVzdCBwdWJsaXNoIGhhdmU/IFBlcmhhcHMgdGhpcyBjb3VsZCBwcm92aWRlIHNvbWUgaW5zaWdodHMgaW50byBpZiBwYWNrYWdlIGF1dGhvcnMgYXJlIGNvbGxhYm9yYXRpbmcgbW9yZSB0aGFuIHRoZXkgdXNlZCB0by4gSXMgdGhlIFIgY29tbXVuaXR5IHdvcmtpbmcgdG9nZXRoZXI/CgpgYGB7cn0Kb3ZfZHQgPC0gb3ZfZHQgfD4gCiAgbXV0YXRlKAogICAgbnVtX2F1dGhvcnMgPSBwdXJycjo6bWFwX2ludCgKICAgICAgLnggPSBhdXRob3IsCiAgICAgIC5mID0gZnVuY3Rpb24oeCl7CiAgICAgICAgeCB8PiAKICAgICAgICAgIHN0cmluZ3I6OnN0cl9zcGxpdCgiLCIsIHNpbXBsaWZ5ID0gVFJVRSkgfD4gCiAgICAgICAgICBsZW5ndGgoKQogICAgICB9CiAgICApCiAgKQpnbGltcHNlKG92X2R0LCAxMDApCmBgYAoKIyMgVGVtcG9yYWwgey50YWJzZXR9CgpUZW1wb3JhbCBmZWF0dXJlcyB0eXBpY2FsbHkgdXNlZnVsIGZvciBhZ2dyZWdhdGlvbiBkb3duc3RyZWFtLgoKYGBge3J9Cmhpc3RfZHQgPC0gaGlzdF9kdCB8PiAKICBtdXRhdGUoCiAgICB5ZWFyID0gbHVicmlkYXRlOjp5ZWFyKGRhdGUpLAogICAgbW9udGggPSBsdWJyaWRhdGU6Om1vbnRoKGRhdGUsIGxhYmVsID0gVFJVRSksCiAgICBkYXkgPSBsdWJyaWRhdGU6OmRheShkYXRlKSwKICAgIHdkYXkgPSBsdWJyaWRhdGU6OndkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSwKICAgIHlyX21vbiA9IHNwcmludGYoIiVkLSVzIiwgeWVhciwgbW9udGgpLAogICAgZHQgPSBsdWJyaWRhdGU6OnltKHBhc3RlMCh5ZWFyLCAiLSIsIG1vbnRoKSkKICApCm92X2R0IDwtIG92X2R0IHw+IAogIGZpbHRlcighaXMubmEoZGF0ZV9wdWJsaXNoZWQpKSB8PgogIG11dGF0ZSgKICAgIHllYXIgPSBsdWJyaWRhdGU6OnllYXIoZGF0ZV9wdWJsaXNoZWQpLAogICAgbW9udGggPSBsdWJyaWRhdGU6Om1vbnRoKGRhdGVfcHVibGlzaGVkLCBsYWJlbCA9IFRSVUUpLAogICAgZGF5ID0gbHVicmlkYXRlOjpkYXkoZGF0ZV9wdWJsaXNoZWQpLAogICAgd2RheSA9IGx1YnJpZGF0ZTo6d2RheShkYXRlX3B1Ymxpc2hlZCwgbGFiZWwgPSBUUlVFKSwKICAgIHlyX21vbiA9IHNwcmludGYoIiVkLSVzIiwgeWVhciwgbW9udGgpLAogICAgZHQgPSBsdWJyaWRhdGU6OnltKHBhc3RlMCh5ZWFyLCAiLSIsIG1vbnRoKSkKICApCmdsaW1wc2Uob3ZfZHQsIDEwMCkKYGBgCgojIyBUaXRsZXMgJiBEZXNjcmlwdGlvbnMgey50YWJzZXR9CgpIb3cgbG9uZyBhcmUgdGhlIHRpdGxlcyBhbmQgZGVzY3JpcHRpb24gZmllbGRzIGluIHRoZSBsYXRlc3QgcGFja2FnZSBzdWJtaXNzaW9ucz8gQW55IGludGVyZXN0aW5nIHRyZW5kcyBvdmVyIHRpbWU/CgpgYGB7cn0Kb3ZfZHQgPC0gb3ZfZHQgfD4KICBtdXRhdGUoCiAgICBsZW5fdGl0bGUgPSBwdXJycjo6bWFwX2ludCh0aXRsZSwgfiBzdHJpbmdyOjpzdHJfY291bnQoLngsICJcXHcrIikpLAogICAgbGVuX2Rlc2MgPSBwdXJycjo6bWFwX2ludChkZXNjcmlwdGlvbiwgfiBzdHJpbmdyOjpzdHJfY291bnQoLngsICJcXHcrIikpCiAgKQpnbGltcHNlKG92X2R0LCAxMDApCmBgYAoKIyMgUGFja2FnZSBMaWNlbnNlcyB7LnRhYnNldH0KClRoZSByYXcgZGF0YXNldCBoYXMgYHIgb3ZfZHQgfD4gY291bnQobGljZW5zZSkgfD4gbnJvdygpYCB1bmlxdWUgbGV2ZWxzIGZvciB0aGUgYGxpY2Vuc2VgIHZhcmlhYmxlLiAKCmBgYHtyfQpvdl9kdCB8PiAKICBjb3VudChsaWNlbnNlKSB8PiAKICByZWFjdGFibGUoY29tcGFjdCA9IFRSVUUpCmBgYAoKQnV0IG1hbnkgb2YgYmVpbmcgcXVpdGUgc2ltaWxhciB0byBlYWNoIG90aGVyLCBzb21lIGJpbm5pbmcgaXMgaW4gb3JkZXIgdG8gZXh0cmFjdCBzb21lIHBhdHRlcm5zLiBIZXJlLCBJIHVzZSB0aGUgYGNhc2Vfd2hlbmAgdG8gYmluIHRvZ2V0aGVyIHNpbWlsYXIgbGljZW5zZXMuIChJJ20gbm8gZXhwZXJ0IGluIHRoZXNlIGxpY2Vuc2VzLiBJJ20gc3VyZSBJJ20gdGFraW5nIHNvbWUgbGliZXJ0aWVzIGluIHRoZSBncm91cGluZyBoZXJlKS4KCmBgYHtyfQpvdl9kdCA8LSBvdl9kdCB8PiAKICBtdXRhdGUoCiAgICBsaWNlbnNlX2NsZWFuZWQgPSBjYXNlX3doZW4oCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIl5HUEwtMyIpIH4gIkdQTC0zIiwKICAgICAgc3RyX2RldGVjdChsaWNlbnNlLCAiXkdQTFxcc1xcKFtcXHNcXGRcXC48PT5dKjMiKSB+ICJHUEwtMyIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIl5HUEwtMiIpIH4gIkdQTC0yIiwKICAgICAgc3RyX2RldGVjdChsaWNlbnNlLCAiXkdQTFxcc1xcKFtcXHNcXGRcXC48PT5dKjIiKSB+ICJHUEwtMiIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIl5BR1BMIikgfiAiQUdQTCIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIl5MR1BMIikgfiAiTEdQTCIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIkFwYWNoZSIpIH4gIkFwYWNoZSIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIkJTRCIpIH4gIkJTRCIsCiAgICAgIHN0cl9kZXRlY3QobGljZW5zZSwgIkxHUEwiKSB+ICJMR1BMIiwKICAgICAgc3RyX2RldGVjdChsaWNlbnNlLCAiTUlUIikgfiAiTUlUIiwKICAgICAgc3RyX2RldGVjdChsaWNlbnNlLCAiQ0MwIikgfiAiQ0MwIiwKICAgICAgbGljZW5zZSA9PSAiR1BMIiB+ICJHUEwiLAogICAgICBUUlVFIH4gIk90aGVyIgogICAgICAjIHN0cl9kZXRlY3QobGljZW5zZSwgIkdOVSIpIH4gIkdOVSIsICMgTGVmdCB0aGVzZSBvdXQgYWZ0ZXIgc29tZSB0cmlhbHMgd2l0aCBwbG90cyBiZWxvdwogICAgICAjIHN0cl9kZXRlY3QobGljZW5zZSwgIk1QTCIpIH4gIk1QTCIsCiAgICAgICMgc3RyX2RldGVjdChsaWNlbnNlLCAiVW5saW1pdGVkIikgfiAiVW5saW1pdGVkIiwKICAgICAgIyBzdHJfZGV0ZWN0KGxpY2Vuc2UsICJeQ0MiKSB+ICJDQyIsCiAgICAgICkKICApCmdsaW1wc2Uob3ZfZHQsIDEwMCkKYGBgCgojIyBEb21haW5zIHsudGFic2V0fQoKV2hpY2ggZG9tYWlucyBkbyBwYWNrYWdlIGF1dGhvcnMgdHlwaWNhbGx5IHVzZT8gTXkgZ3Vlc3MgaXMgR2l0SHViIHJ1bGVzIHRoZW0gYWxsLCBidXQgaXMgdGhhdCB0cnVlPyBDYW4gd2Ugc2VlIGFueSByaXNlIG9mIG90aGVyIG9mZmVyaW5ncyBsaWtlIEdpdExhYiBvciBCaXRCdWNrZXQ/CgpgYGB7cn0Kb3ZfZHQgPC0gb3ZfZHQgfD4KICBtdXRhdGUodXJsX2RvbWFpbiA9IG1hcF9jaHIodXJsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXMubmEoLngpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKE5BKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybih1cmxfcGFyc2UoLngpJGRvbWFpbikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksCiAgICAgICAgIGJ1Z19kb21haW4gPSBtYXBfY2hyKGJ1Z19yZXBvcnRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXMubmEoLngpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKE5BKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybih1cmxfcGFyc2UoLngpJGRvbWFpbikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkpCmdsaW1wc2Uob3ZfZHQsIDEwMCkKYGBgCgojIEdyYXBoaWNhbCBFREEKCk5vdyB0aGF0IEkgaGF2ZSB0aGUgZGF0YSBzZXRzIHByZXBhcmVkIGFuZCByZWFkeSwgaXQncyB0aW1lIGZvciB0aGUgZnVuIHBhcnQgLSBiZWluZyBjcmVhdGl2ZSBhbmQgY3JlYXRpbmcgc29tZSBpbnRlcmVzdGluZyB2aXN1YWxzISBMZXQncyBhdHRhY2sgdGhvc2UgcXVlc3Rpb25zIG9uZSBhdCBhIHRpbWUuCgoxLiBIb3cgbG9uZyBkaWQgcGFja2FnZXMgdGFrZSBmcm9tIHRoZWlyIGZpcnN0IHJlbGVhc2UgdG8gdmVyc2lvbiAxLjA/CjEuIFdoaWNoIHBhY2thZ2VzIGhhdmUgaGFkIHRoZSBtb3N0IHZlcnNpb24gdXBkYXRlcz8KMS4gV2hhdCB0eXBlIG9mIHBhY2thZ2VzIHdlcmUgbW9zdCBmcmVxdWVudCBpbiBkaWZmZXJlbnQgeWVhcnM/CjEuIFdobyBhcmUgdGhlIG1vc3QgcHJvZHVjdGl2ZSBhdXRob3JzPwoxLiBDYW4geW91IHByZWRpY3QgdGhlIGdyb3d0aCB0b3dhcmQgMjAyNT8KMS4gV2hhdCBsaWNlbnNlIGlzIG1vc3QgdXNlZD8gSGFzIHRoZXJlIGJlZW4gYSBjaGFuZ2Ugb3ZlciB0aW1lPwoxLiBIb3cgbWFueSBwYWNrYWdlcyB1c2UgYWxsIENBUFMsIGFsbCBzbWFsbCwgb3IgYSBtaXh0dXJlPwoxLiBXaGljaCByZXBvc2l0b3JpZXMgZG8gcGFja2FnZXMgdXNlPyBHaXRodWIvQml0YnVja2V0IGV0Yy4gSG93IGRvIHRoZXNlIHZhcnkgb3ZlciB0aW1lPwoxLiBEbyBwYWNrYWdlcyBoYXZlIFVSTHMgZm9yIGJ1ZyByZXBvcnRzPwoxLiBJcyB0aGVyZSBhbnkgdGVtcG9yYWwgcGF0dGVybnMgdG8gd2hlbiB2ZXJzaW9ucyBhcmUgc3VibWl0dGVkIHRvIENSQU4/CjEuIEhhdmUgdGl0bGVzICYgZGVzY3JpcHRpb25zIGdvdHRlbiBsb25nZXIgb3ZlciB0aW1lPwoxLiBEbyBhdXRob3JzIHVzZSBtaW5vciB2ZXJzaW9ucz8KCiMjIFBhY2thZ2UgRGVwZW5kZW5jaWVzCgpfUTogSG93IGhhdmUgdGhlIGRlcGVuZGVuY2llcyAmIGltcG9ydHMgY2hhbmdlZCBvdmVyIHRpbWU/XwoKVGFraW5nIHRoZSBsYXN0IHB1Ymxpc2hlZCB5ZWFyLW1vbnRoIGNvbWJvIGZvciB0aGUgYWN0aXZlIHBhY2thZ2VzIGluIHRoZSByZXBvLCBJIGNhbiBjYWxjdWxhdGUgdGhlIG1lZGlhbiB2YWx1ZXMgZm9yIGRlcGVuZGVuY2llcyBhbmQgaW1wb3J0cy4gTWVkaWFucyB3aWxsIGJlIHJvYnVzdCBhZ2FpbnN0IG91dGxpZXJzLCB3aGlsZSBhbHNvIGNvbnZpbmllbnRseSBnaXZpbmcgdXMgd2hvbGUgbnVtYmVycy4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpvdl9kdCB8PiAKICBzZWxlY3QoZGF0ZV9wdWJsaXNoZWQsIGR0LCBudW1faW1wb3J0cykgfD4gCiAgdGltZXRrOjpwYWRfYnlfdGltZShkYXRlX3B1Ymxpc2hlZCwgLnBhZF92YWx1ZSA9IDApIHw+IAogIG11dGF0ZShkdCA9IGx1YnJpZGF0ZTo6eW0ocGFzdGUwKGx1YnJpZGF0ZTo6eWVhcihkYXRlX3B1Ymxpc2hlZCksICItIiwgbHVicmlkYXRlOjptb250aChkYXRlX3B1Ymxpc2hlZCkpKSkgfD4gCiAgZ3JvdXBfYnkoZHQpIHw+CiAgbXV0YXRlKG1lZGlhbl9pbXBvcnRzID0gbWVkaWFuKG51bV9pbXBvcnRzKSkgLT4gcGxvdF9kdAoKcGxvdF9kdCB8PiAKICBnZ3Bsb3QoYWVzKHggPSBkdCkpICsKICBnZW9tX3BvaW50KGFlcyh5ID0gbnVtX2ltcG9ydHMpLCBhbHBoYSA9IDAuMDEpICsKICBnZW9tX3Ntb290aChhZXMoeSA9IG1lZGlhbl9pbXBvcnRzKSwgY29sb3IgPSAicmVkIikgKwogIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcyA9ICIxIHllYXIiLCBkYXRlX2xhYmVscyA9ICInJXkiLCBtaW5vcl9icmVha3MgPSBOVUxMLCBleHBhbmQgPSBjKE5BLCAwLjIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG1pbm9yX2JyZWFrcyA9IE5VTEwpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnguYm90dG9tID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGNvbG91ciA9ICJncmF5IiksCiAgICBheGlzLnRpY2tzLnkubGVmdCA9IGVsZW1lbnRfbGluZShzaXplID0gMC41LCBjb2xvdXIgPSAiZ3JheSIpLAogICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMCw1MCwwLDUwKQogICkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJIb3cgaGF2ZSBgaW1wb3J0c2AgY2hhbmdlZCBvdmVyIHRpbWU/IiwKICAgIHggPSBOVUxMLCB5ID0gTlVMTAogICkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpICsKICBhbm5vdGF0ZSgKICAgICJ0ZXh0IiwKICAgIHggPSBtYXgocGxvdF9kdCRkYXRlX3B1Ymxpc2hlZCkrbHVicmlkYXRlOjpkZGF5cyg5MCksCiAgICB5ID0gbWF4KHBsb3RfZHQkbWVkaWFuX2ltcG9ydHMpLAogICAgbGFiZWwgPSBzcHJpbnRmKCJNZWRpYW46ICVkIiwgbWF4KHBsb3RfZHQkbWVkaWFuX2ltcG9ydHMpKSwKICAgIHZqdXN0ID0gMSwKICAgIGhqdXN0ID0gMCwKICAgIGNvbG9yID0gInJlZCIKICApCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTV9CmRlcHMgPC0gb3ZfZHQgfD4gCiAgc2VsZWN0KHllYXIsIGxlbl9kZXNjLCBsZW5fdGl0bGUpIHw+IAogIGFycmFuZ2UoLXllYXIpIHw+IAogIGZpbHRlcighaXMubmEoeWVhciksIHllYXIgPiAyMDA4KSB8PiAKICBtdXRhdGUoeWVhciA9IGZhY3Rvcih5ZWFyLCBsZXZlbHMgPSBzZXEoMjAwOCwgMjAyMikpKQpkZXBzIHw+CiAgcGl2b3RfbG9uZ2VyKC15ZWFyKSB8PiAKICBnZ3Bsb3QoYWVzKHkgPSB5ZWFyLCB4ID0gdmFsdWUsIGZpbGwgPSBuYW1lKSkgKwogIHN0YXRfZGVuc2l0eV9yaWRnZXMoCiAgICBiYW5kd2lkdGggPSA0LAogICAgaml0dGVyZWRfcG9pbnRzID0gRiwKICAgIHBvc2l0aW9uID0gcG9zaXRpb25fcG9pbnRzX2ppdHRlcihoZWlnaHQgPSAwKSwKICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NoYXBlID0gInwiLAogICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDIsCiAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMC4yNSwKICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gLjk1LAogICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGVfbGluZXMgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlcyA9IDIsCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNywgCiAgICAgICAgICAgICAgICAgICAgICByZWxfbWluX2hlaWdodCA9IDAuMDEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDIwMCksIGV4cGFuZCA9IGMoMCwwKSkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpICsKICB0aGVtZV9yaWRnZXMoY2VudGVyID0gVFJVRSkKYGBgCgotICAgSGF2ZSB0aXRsZXMgJiBkZXNjcmlwdGlvbnMgZ290dGVuIGxvbmdlciBvdmVyIHRpbWU/CgpgYGB7cn0Kb3ZfZHQgfD4gCiAgICBncm91cF9ieShkdCkgfD4gCiAgICBzdW1tYXJpc2VfYXQodmFycyhsZW5fdGl0bGUsIGxlbl9kZXNjKSwgbGlzdChtZWRpYW4gPSBtZWRpYW4sIHNkID0gc2QpLCBuYS5ybSA9IFRSVUUpIHw+IAogIGdncGxvdChhZXMoeD0gZHQpKSArCiAgICBnZW9tX2ppdHRlcihhZXMoeSA9IGxlbl90aXRsZV9tZWRpYW4sIGNvbG9yID0gImxlbl90aXRsZV9tZWRpYW4iKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3Ntb290aChhZXMoeSA9IGxlbl90aXRsZV9tZWRpYW4sIGNvbG9yID0gImxlbl90aXRsZV9tZWRpYW4iKSwgc3BhbiA9IDAuMywgc2UgPSBGQUxTRSkgKwogIGdlb21faml0dGVyKGFlcyh5ID0gbGVuX2Rlc2NfbWVkaWFuLCBjb2xvciA9ICJsZW5fZGVzY19tZWRpYW4iKSwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3Ntb290aChhZXMoeSA9IGxlbl9kZXNjX21lZGlhbiwgY29sb3IgPSAibGVuX2Rlc2NfbWVkaWFuIiksIHNwYW4gPSAwLjMsIHNlID0gRkFMU0UpCiAgIyB0aGVtZV9saWdodCgpCmBgYAoKYGBge3J9Cm92X2R0IHw+IAogIGZpbHRlcih5ZWFyICVpbiUgYygyMDIyLCAyMDIwLCAyMDE4KSkgfD4gCiAgZ2dwbG90KCkgKwogIGdlb21fZGVuc2l0eShhZXMoeCA9IGxlbl9kZXNjLCAKICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcy5mYWN0b3IoeWVhciksIAogICAgICAgICAgICAgICAgICAgY29sb3IgPSBhcy5mYWN0b3IoeWVhcikKICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgIGFscGhhID0gMC4zCiAgICAgICAgICAgICAgICkKb3ZfZHQgfD4gCiAgZmlsdGVyKHllYXIgJWluJSBjKDIwMjIsIDIwMjAsIDIwMTgpKSB8PiAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBsZW5fZGVzYywgCiAgICAgICAgICAgICAgICAgICBmaWxsID0gYXMuZmFjdG9yKHllYXIpLCAKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYXMuZmFjdG9yKHllYXIpCiAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICBhbHBoYSA9IDAuMwogICAgICAgICAgICAgICApICsKICBmYWNldF93cmFwKH55ZWFyKQpgYGAKCmBgYHtyfQpvdl9kdCB8PiAKICBnZ3Bsb3QoYWVzKHg9IGRhdGVfcHVibGlzaGVkLCB5ID0gbGVuX3RpdGxlKSkgKwogIGdlb21faml0dGVyKGFscGhhID0gMC4wNSkgKwogIGdlb21fc21vb3RoKHNwYW4gPSAwLjEsIHNlID0gRkFMU0UpICsKICB0aGVtZV9saWdodCgpICsKICBzY2FsZV95X2xvZzEwKCkKCm92X2R0IHw+IAogIGdncGxvdChhZXMoeD0gZGF0ZV9wdWJsaXNoZWQsIHkgPSBsZW5fZGVzYykpICsKICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMDUpICsKICBnZW9tX3Ntb290aChzcGFuID0gMC4yLCBzZSA9IEZBTFNFKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgc2NhbGVfeV9sb2cxMCgpCmBgYAoKYGBge3J9CgpgYGAKCi0gICBXaGF0IGxpY2Vuc2UgaXMgbW9zdCB1c2VkPyBIYXMgdGhlcmUgYmVlbiBhIGNoYW5nZSBvdmVyIHRpbWU/CgpgYGB7cn0Kb3ZfZHQgfD4gCiAgZ3JvdXBfYnkobGljZW5zZV9jbGVhbmVkKSB8PiAKICBjb3VudCgpIHw+IAogIGdncGxvdChhZXMoeCA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKGxpY2Vuc2VfY2xlYW5lZCwgbiksIHkgPSBuLCBmaWxsID0gbGljZW5zZV9jbGVhbmVkKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikKYGBgCgpgYGB7cn0KcGxvdF9idWJibGVzIDwtIGZ1bmN0aW9uKGRhdCwKICAgICAgICAgICAgICAgICAgICAgICAgIC5zY2FsZSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfcmFkaXVzLAogICAgICAgICAgICAgICAgICAgICAgICAgYnViYmxlX3JhZGl1cywKICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWF4aXRlcikgewogIC5xdHkgPC0gbnJvdyhkYXQpCiAgCiAgdGhldGEgPC0gc2VxKDAsIDM2MCwgbGVuZ3RoLm91dCA9IC5xdHkgKyAxKQogIAogIGRhdCR4IDwtIHBsb3RfcmFkaXVzICogY29zKHRoZXRhICogcGkgLyAxODApWy0xXQogIGRhdCR5IDwtIHBsb3RfcmFkaXVzICogc2luKHRoZXRhICogcGkgLyAxODApWy0xXQogIGRhdCRuX3NjYWxlZCA8LSBkYXQkbiAvIC5zY2FsZQogIAogIHhwYWNrIDwtIHJlcChkYXQkeCwgdGltZXMgPSBkYXQkbl9zY2FsZWQpCiAgeXBhY2sgPC0gcmVwKGRhdCR5LCB0aW1lcyA9IGRhdCRuX3NjYWxlZCkKICAKICBjb29yZHMgPC0gdGliYmxlKAogICAgeCA9IHhwYWNrICsgcnVuaWYobGVuZ3RoKHhwYWNrKSksCiAgICB5ID0geXBhY2sgKyBydW5pZihsZW5ndGgoeXBhY2spKSwKICAgIHIgPSBidWJibGVfcmFkaXVzCiAgKQogIAogIHBhY2tlZF9jb29yZHMgPC0KICAgIGNpcmNsZVJlcGVsTGF5b3V0KGNvb3Jkcywgc2l6ZXR5cGUgPSAiciIsIG1heGl0ZXIgPSBtYXhpdGVyKQogIAogIHBhY2tlZF9jb29yZHMkbGF5b3V0IHw+CiAgICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgICBnZW9tX3BvaW50KGFlcyhzaXplID0gcmFkaXVzKSwgYWxwaGEgPSBhbHBoYSkgKwogICAgY29vcmRfZXF1YWwoKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgdGhlbWUoCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgICApICsKICAgIGdlb21fdGV4dCgKICAgICAgYWVzKAogICAgICAgIHggPSB4LAogICAgICAgIHkgPSB5LAogICAgICAgIGxhYmVsID0gbGFiZWwKICAgICAgKSwKICAgICAgZGF0YSA9IGRhdCwKICAgICAgaGp1c3QgPSAiY2VudGVyIiwKICAgICAgdmp1c3QgPSAiY2VudGVyIgogICAgKQp9Cgpvdl9kdCB8PiAKICAgIGNvdW50KGxpY2Vuc2VfY2xlYW5lZCkgfD4gCiAgICB0b3Bfbig2LCBuKSB8PiAKICBtdXRhdGUobGFiZWwgPSBzcHJpbnRmKCIlc1xuJWQgUGtncyIsIGxpY2Vuc2VfY2xlYW5lZCwgbikpIHw+IAogICAgYXJyYW5nZShydW5pZigxOm4oKSkpIHw+IAogIHBsb3RfYnViYmxlcygKICAgIC5zY2FsZSA9IDEwMCwKICAgIHBsb3RfcmFkaXVzID0gMTAsCiAgICBidWJibGVfcmFkaXVzID0gMC41NiwKICAgIGFscGhhID0gMC4yLAogICAgbWF4aXRlciA9IDEwMDAKICApCgoKIyAuc2NhbGUgPC0gMTAwCiMgLnF0eSA8LSA2CiMgbGljIDwtIG92X2R0IHw+IAojICAgZ3JvdXBfYnkobGljZW5zZV9jbGVhbmVkKSB8PiAKIyAgIGNvdW50KCkgfD4gCiMgICBhcnJhbmdlKC1uKSB8PiAKIyAgIGhlYWQoLnF0eSkgfD4KIyAgIG11dGF0ZShuX3NjYWxlZCA9IHJvdW5kKG4gLyAuc2NhbGUpKSB8PiAKIyAgIGFycmFuZ2UocnVuaWYoMTpuKCkpKQojICAgCiMgciA8LSAxMAojIHRoZXRhIDwtIHNlcSgwLCAzNjAsIGxlbmd0aC5vdXQgPSAucXR5KzEpCiMgbGljJHggPC0gciAqIGNvcyh0aGV0YSAqIHBpIC8gMTgwKVstMV0KIyBsaWMkeSA8LSByICogc2luKHRoZXRhICogcGkgLyAxODApWy0xXQojIHhwYWNrIDwtIHJlcChsaWMkeCwgdGltZXM9bGljJG5fc2NhbGVkKQojIHlwYWNrIDwtIHJlcChsaWMkeSwgdGltZXM9bGljJG5fc2NhbGVkKQojIAojIGNvb3JkcyA8LSB0aWJibGUoeD14cGFjaytydW5pZihsZW5ndGgoeHBhY2spKSwKIyAgICAgICAgICAgICAgICAgICAgICB5PXlwYWNrK3J1bmlmKGxlbmd0aCh5cGFjaykpLAojICAgICAgICAgICAgICAgICAgICAgIHI9LjU2KQojIHBhY2tlZF9jb29yZHMgPC0gY2lyY2xlUmVwZWxMYXlvdXQoY29vcmRzLCBzaXpldHlwZT0iciIsIG1heGl0ZXI9MTAwMCkKIyBwYWNrZWRfY29vcmRzJGxheW91dCB8PiAKIyAgIGdncGxvdChhZXMoeCwgeSkpICsKIyAgIGdlb21fcG9pbnQoYWVzKHNpemUgPSByYWRpdXMpLCBhbHBoYSA9IDAuMikgKwojICAgY29vcmRfZXF1YWwoKSArCiMgICB0aGVtZV9taW5pbWFsKCkgKwojICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAojICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKIyAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiMgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkpICsKIyAgIGdlb21fdGV4dChhZXMoeCA9IHgsIAojICAgICAgICAgICAgICAgICB5ID0geSwgCiMgICAgICAgICAgICAgICAgIGxhYmVsID0gc3ByaW50ZigiJXNcbiVkIFBrZ3MiLCBsaWNlbnNlX2NsZWFuZWQsIG4pKSwKIyAgICAgICAgICAgICBkYXRhID0gbGljLAojICAgICAgICAgICAgIGhqdXN0ID0gImNlbnRlciIsIAojICAgICAgICAgICAgIHZqdXN0ID0gImNlbnRlciIpIAoKYGBgCgpgYGB7cn0Kb3ZfZHQgfD4gCiAgZ3JvdXBfYnkoZHQpIHw+IAogIGNvdW50KGxpY2Vuc2VfY2xlYW5lZCkgfD4gCiAgbXV0YXRlKGxpY2Vuc2VfY2xlYW5lZCA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKGxpY2Vuc2VfY2xlYW5lZCwgbikpIHw+IAogIGdncGxvdChhZXMoeD0gZHQsIHkgPSBuLCBjb2xvciA9IGxpY2Vuc2VfY2xlYW5lZCkpICsKICAjIGdlb21fbGluZSggYWxwaGEgPSAwLjMpICsKICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMykgKwogIGdlb21fc21vb3RoKHNwYW4gPSAwLjMsIHNlID0gRkFMU0UpICsKICB0aGVtZV9saWdodCgpCmBgYAoKLSAgIERvIHBhY2thZ2VzIGhhdmUgVVJMcyBmb3IgYnVnIHJlcG9ydHM/CgpgYGB7cn0Kb3ZfZHQgfD4gCiAgZ3JvdXBfYnkoZHQpIHw+IAogIGNvdW50KHVybF9leGlzdCA9IGlzLm5hKHVybCkpIHw+ICAKICBnZ3Bsb3QoYWVzKHg9IGR0LCB5ID0gbiwgY29sb3IgPSB1cmxfZXhpc3QpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjMpICsKICBnZW9tX3Ntb290aChzcGFuID0gMC4zLCBzZSA9IEZBTFNFKSArCiAgdGhlbWVfbGlnaHQoKQpvdl9kdCB8PiAKICBncm91cF9ieShkdCkgfD4gCiAgY291bnQodXJsX2V4aXN0ID0gaXMubmEoYnVnX3JlcG9ydHMpKSB8PiAgCiAgZ2dwbG90KGFlcyh4PSBkdCwgeSA9IG4sIGNvbG9yID0gdXJsX2V4aXN0KSkgKwogIGdlb21faml0dGVyKGFscGhhID0gMC4zKSArCiAgZ2VvbV9zbW9vdGgoc3BhbiA9IDAuMywgc2UgPSBGQUxTRSkgKwogIHRoZW1lX2xpZ2h0KCkKYGBgCgotICAgV2hpY2ggcmVwb3NpdG9yaWVzIGRvIHBhY2thZ2VzIHVzZT8gR2l0aHViL0JpdGJ1Y2tldCBldGMuIEhvdyBkbyB0aGVzZSB2YXJ5IG92ZXIgdGltZT8KCmBgYHtyfQpvdl9kdCB8PiAKICBmaWx0ZXIoZG9tYWluICE9ICIiKSB8PiAKICBtdXRhdGUoZG9tYWluID0gZm9yY2F0czo6ZmN0X2x1bXBfbWluKGRvbWFpbiwgMjApKSB8PiAKICBncm91cF9ieShkdCkgfD4gCiAgY291bnQoZG9tYWluKSB8PiAgCiAgZ2dwbG90KGFlcyh4PSBkdCwgeSA9IG4sIGNvbG9yID0gZG9tYWluKSkgKwogIGdlb21faml0dGVyKGFscGhhID0gMC4zKSArCiAgZ2VvbV9zbW9vdGgoc3BhbiA9IDAuNSwgc2UgPSBGQUxTRSkgKwogIHRoZW1lX2xpZ2h0KCkKYGBgCgpgYGB7cn0Kb3ZfZHQgfD4gCiAgIGZpbHRlcihkb21haW4gIT0gIiIpIHw+IAogIG11dGF0ZShkb21haW4gPSBmb3JjYXRzOjpmY3RfbHVtcF9taW4oZG9tYWluLCAyMCkpIHw+IAogICAgY291bnQoZG9tYWluKSB8PiAKICBtdXRhdGUobGFiZWwgPSBzcHJpbnRmKCIlc1xuJWQiLCBkb21haW4sIG4pKSB8PiAKICAgIGFycmFuZ2UocnVuaWYoMTpuKCkpKSB8PiAKICBwbG90X2J1YmJsZXMoCiAgICAuc2NhbGUgPTUwLAogICAgcGxvdF9yYWRpdXMgPSA2LAogICAgYnViYmxlX3JhZGl1cyA9IDAuNCwKICAgIGFscGhhID0gMC4yLAogICAgbWF4aXRlciA9IDEwMDAKICApCmBgYAoKLSAgIElzIHRoZXJlIGFueSB0ZW1wb3JhbCBwYXR0ZXJucyB0byB3aGVuIHZlcnNpb25zIGFyZSBzdWJtaXR0ZWQgdG8gQ1JBTj8KCmBgYHtyfQpvdl9kdCB8PiAKICBmaWx0ZXIoIWlzLm5hKGR0KSwgZHQgPCAiMjAyMi0wNy0wMSIpIHw+IAogIGNvdW50KGR0KSB8PiAKICBhcnJhbmdlKGR0KSB8PiAKICB0aW1ldGs6OnBhZF9ieV90aW1lKGR0LCAuYnkgPSAibW9udGgiLCAucGFkX3ZhbHVlID0gMCkgfD4gCiAgZ2dwbG90KGFlcyhkdCwgbikpICsKICBnZW9tX2xpbmUoKQoKb3ZfZHQgfD4gCiAgZmlsdGVyKCFpcy5uYShkdCksIGR0IDwgIjIwMjItMDctMDEiKSB8PiAKICBjb3VudChkdCkgfD4gCiAgYXJyYW5nZShkdCkgfD4gCiAgdGltZXRrOjpwYWRfYnlfdGltZShkdCwgLmJ5ID0gIm1vbnRoIiwgLnBhZF92YWx1ZSA9IDApIC0+IHhkYXQKdGltZXRrOjpwbG90X3NlYXNvbmFsX2RpYWdub3N0aWNzKHhkYXQsIGR0LCBsb2cobiksIC5pbnRlcmFjdGl2ZSA9IEZBTFNFKQoKdGltZXRrOjpwbG90X3N0bF9kaWFnbm9zdGljcyh4ZGF0IHw+IGZpbHRlcihkdCA+ICIyMDE4LTAxLTAxIiwgZHQgPCAiMjAyMi0wNy0wMSIpLCBkdCwgbiwgLmludGVyYWN0aXZlID0gRkFMU0UsIC5mZWF0dXJlX3NldCA9IGMoIm9ic2VydmVkIiwgInNlYXNvbiIsICJ0cmVuZCIsICJyZW1haW5kZXIiKSkKYGBgCg==